Getting started

First, we need to load our packages like we did in our previous notebook.

library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.3.0
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.4     ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(snotelr)
library(cowplot)

Attaching package: ‘cowplot’

The following object is masked from ‘package:lubridate’:

    stamp
theme_set(theme_cowplot())
library(knitr)
library(Kendall)
library(trend)

Next, we will source the functions I’ve created to derive water year information and compute snow metrics. These files can be found in analysis/functions.

source("functions/snow_metrics.R")
source("functions/meteo_metrics.R")
source("functions/water_year.R")

You can see these functions now in your Environment pane. All the source function does is run the R scripts that contain the functions I’ve built. (We’ll return to the actual functions later.)

Defining our subset

Useful metadata

In our work we will want to use all or most of the long-term SNOTEL records, but here we’ll start with a subset. To start building this subset, we’ll need a couple data sources. One is the SNOTEL metadata we can grab from snotelr:

snotel_info <- snotel_info()
snotel_info %>% 
  head()

The other is the HARBOR dataset from Scott Peckham:

harbor_url <- "https://raw.githubusercontent.com/peckhams/nextgen_basin_repo/refs/heads/main/__Collated/collated_basins_all.tsv"
basin_info <- read_tsv(harbor_url)
Rows: 30717 Columns: 51── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (42): Site_ID, NWS_Loc_ID, GOES_ID, RFC, WFO/CWA, HSA, HUC, Site_Name, Site_Type, Stage_Data, PEDTS_Obs, State_Code, C...
dbl  (9): Lon, Lat, Area, Minlon, Maxlon, Minlat, Maxlat, Closest_Site_Dist, HLR_Code_Outlet
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
basin_info %>% 
  head()

We discussed the SNOTEL file in our previous notebook. The HARBOR (Harmonized Attributes of River Basins in One Repo) dataset is an exhaustive accounting of sometimes overlapping basin data sources from USGS NWIS, CAMELS, NWS River Forecast Centers, etc.

At first glance these two datasets have nothing in common, but buried in the description column of snotel_info is a HUC (hydrologic unit code) ID that we can match to the HUC column in basin_info. We have to do a bit of string manipulation first.

snotel_info <- snotel_info %>% 
  mutate(HUC = stringr::str_extract(string = description,
                                           pattern = "(?<=\\().*(?=\\))"))

Now we that we’ve extracted the HUC ID from in between the parentheses, we can join the two datasets.

all_info <- left_join(snotel_info,
                 basin_info,
                 by = "HUC")
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.

The warning above indicates there are some rows in snotel_info that have multiple matches in basin_info and vice versa. This occurs when there are multiple SNOTEL stations in a given HUC or when a SNOTEL station finds itself in multiple nested basins.

Using metadata to select SNOTEL stations

The combined dataframe includes multiple columns we can use to split the data. A few examples:

  • All SNOTEL stations in a USGS GAGES II Reference basin
    • 94 matches
  • SNOTEL stations above 1500 m in Oregon
    • 54 matches
  • SNOTEL stations in the WestMtns ecoregion within a CAMELS basin with a snow-dom hydrograph type
    • 1 match
  • And so on…

For now, we’ll start with a relatively small SNOTEL and USGS Gages II Reference subset with at least 40 yrs of data.

subset_info <- all_info %>% 
  filter(year(start) <= 1985 & year(end) >= 2024) %>% 
  filter(Is_GAGES2_Ref == "Y")

There are some duplicates in the subset, so we’ll filter to just the highest elevation basins (assuming they’re more representative of the SNOTEL-observed snow conditions).

subset_info <- subset_info %>% 
  group_by(site_id) %>% 
  slice_max(order_by = Elev, with_ties = FALSE) %>% 
  ungroup()

Now we have our subset of 33 SNOTEL stations.

SNOTEL data

Accessing station data

Now we’ll identify the site_id for each station in our subset, put it in a vector, and download the data with snotelr.

sites <- subset_info %>% pull(site_id)

NOTE: If you don’t want to wait while snotelr downloads the dataset, skip ahead to the commented-out cell that says df <- readRDS("../data/snotel_camels_subset.RDS"), uncomment it, and run it.

time_start = Sys.time()
df <- snotel_download(sites, internal = T)
Downloading site: hewinta , with id: 521

Downloading site: hole-in-rock , with id: 528

Downloading site: hams fork , with id: 509

Downloading site: echo peak , with id: 463

Downloading site: rubicon #2 , with id: 724

Downloading site: lamoille #3 , with id: 570

Downloading site: munson ridge , with id: 950

Downloading site: ward creek #3 , with id: 848

Downloading site: summit ranch , with id: 802

Downloading site: mt hood test site , with id: 651

Downloading site: lasal mountain , with id: 572

Downloading site: corral pass , with id: 418

Downloading site: olallie meadows , with id: 672

Downloading site: hagans meadow , with id: 508

Downloading site: heavenly valley , with id: 518

Downloading site: independence lake , with id: 541

Downloading site: joe wright , with id: 551

Downloading site: vail mountain , with id: 842

Downloading site: franklin basin , with id: 484

Downloading site: mud ridge , with id: 655

Downloading site: north fork , with id: 666

Downloading site: daniels-strawberry , with id: 435

Downloading site: pickle keg , with id: 691

Downloading site: steel creek park , with id: 790

Downloading site: vernon creek , with id: 844

Downloading site: dome lake , with id: 451

Downloading site: little warm , with id: 585

Downloading site: shell creek , with id: 751

Downloading site: spring creek divide , with id: 779

Downloading site: many glacier , with id: 613

Downloading site: bear creek , with id: 321

Downloading site: west yellowstone , with id: 924

Downloading site: northeast entrance , with id: 670
time_end = Sys.time()
time_end - time_start
Time difference of 3.107575 mins

First, we’ll select just the columns we need.

# Downselect to just the columns we want
# Rename var columns to include units
df <- df %>% 
  select(site_id, date,
         swe_mm = snow_water_equivalent,
         snow_depth_mm = snow_depth,
         ppt_mm = precipitation,
         tair_av_degC = temperature_mean)

Then we’ll add date information:

# Add additional date information
df <- df %>% 
  mutate(date = ymd(date),
         wyear = wateryear(date),
         dowy = day_of_wateryear(date))

Then we’ll save it as an RDS file (I’ve commented this part out because it doesn’t need to re-run).

Note: I saved the file for two reasons: 1) in case we have bandwidth issues and 2) to analyze again in the next notebook.

# saveRDS(object = df,
#         file = "../data/snotel_camels_subset.RDS")

We can uncomment out the following if we need to import the saved data.

# df <- readRDS("../data/snotel_camels_subset.RDS")

Pre-processing the data

We want to only include years in our analysis with a certain percentage of valid SWE observations. We’re going to make that threshold 100% here (SNOTEL SWE has a relatively robust QC protocol), but you can choose other values.

# Calculate the percentage of valid SWE observations per water year
site_summary_by_wyear <- df %>% 
  group_by(site_id, wyear) %>% 
  summarize(n_expected = ifelse(any(wyear %% 4 == 0),
                                366,
                                365),
            n_obs = sum(!is.na(swe_mm)),
            pct_valid = (n_obs / n_expected) * 100) %>% 
  ungroup()
`summarise()` has grouped output by 'site_id'. You can override using the `.groups` argument.
# Provide a threshold of valid obs that we'll consider to be a complete water year
pct_valid_thresh = 100

# Identify sites and water years that meet our threshold
valid_sites_wyears <- site_summary_by_wyear %>% 
  filter(pct_valid >= pct_valid_thresh) %>% 
  select(site_id, wyear)

# Filter using inner join to only sites and water years in our valid data frame
df_filter <- 
  inner_join(df, valid_sites_wyears,
             by = c("site_id", "wyear"))

Calculate metrics

Now we’ll use our SNOTEL data subset to compute various metrics:

metrics <- df_filter %>% 
  group_by(site_id, wyear) %>% 
  summarize(max_swe_mm = maxSWE(swe_mm), 
            max_swe_dowy = maxSWE_DOWY(swe_mm, dowy), 
            scd_days = scd(swe_mm),
            snow_on_day = firstSnow(swe_mm, dowy),
            snow_off_day = lastSnow(swe_mm, dowy, max_swe_dowy),
            melt_season_days = meltSeason(max_swe_dowy, snow_off_day),
            melt_rate_mm_d = meltRate(melt_season_days, max_swe_mm),
            ssm = snowSeasonality(swe_mm), 
            swe_apr1_mm = april1SWE(swe_mm, dowy, wyear), 
            pre_max_swe_melt_mm = preMaxMelt(swe_mm, dowy, max_swe_dowy),
            pre_max_swe_melt_total_melt_pct = preMaxMeltPctTotalMelt(pre_max_swe_melt_mm, swe_mm),
            pre_max_swe_melt_max_swe_ratio = preMaxMeltMaxSWERatio(pre_max_swe_melt_mm, max_swe_mm),
            melt_com_day = meltCoM(swe_mm, dowy),
            swe_to_ppt_ratio = sweToPptRatio(max_swe_mm, ppt_mm),
            fall_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "fall"),
            winter_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "winter"),
            spring_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "spring"),
            summer_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "summer"),
            annual_ppt_mm = totalPrecip(ppt_mm),
            fall_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "fall"),
            winter_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "winter"),
            spring_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "spring"),
            summer_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "summer"),
            annual_tair_degC = meanTemp(tair_av_degC))
`summarise()` has grouped output by 'site_id'. You can override using the `.groups` argument.
# Export data 
saveRDS(object = metrics,
        file = "../data/snotel_camels_subset_metrics.RDS")

We can take a quick look at these tabular data.

metrics %>% 
  head()

We can also plot some of the outcomes.

Maximum SWE

ggplot(metrics, aes(wyear, max_swe_mm)) + 
  geom_line() + 
  facet_wrap(~as.factor(site_id), ncol = 4) +
  labs(x = "Water Year", y = "Max SWE (mm)")

Snow cover duration (SCD)

ggplot(metrics, aes(wyear, scd_days)) + 
  geom_line() + 
  facet_wrap(~as.factor(site_id), ncol = 4) +
  labs(x = "Water Year", y = "SCD (d)")

Snow seasonality metric (SSM)

ggplot(metrics, aes(wyear, ssm)) + 
  geom_line() + 
  facet_wrap(~as.factor(site_id), ncol = 4) +
  labs(x = "Water Year", y = "SSM")

Pre-max SWE snowmelt as percent of total snowmelt

ggplot(metrics, aes(wyear, pre_max_swe_melt_total_melt_pct)) + 
  geom_line() + 
  facet_wrap(~as.factor(site_id), ncol = 4) +
  labs(x = "Water Year", y = "Pre-Max SWE Melt (%)")

However, it is hard to tell what, if anything, is happening at our sites over time. So what we’ll do next is compute some trends.

LS0tCnRpdGxlOiAnU3RlcCAwMjogQ29tcHV0aW5nIFJlbGV2YW50IFNub3cgTWV0cmljcyBvbiBhIFN1YnNldCBvZiBTTk9URUwgU3RhdGlvbnMnCmF1dGhvcjogIktlaXRoIEplbm5pbmdzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgR2V0dGluZyBzdGFydGVkCgpGaXJzdCwgd2UgbmVlZCB0byBsb2FkIG91ciBwYWNrYWdlcyBsaWtlIHdlIGRpZCBpbiBvdXIgcHJldmlvdXMgbm90ZWJvb2suCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc25vdGVscikKbGlicmFyeShjb3dwbG90KQp0aGVtZV9zZXQodGhlbWVfY293cGxvdCgpKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KEtlbmRhbGwpCmxpYnJhcnkodHJlbmQpCmBgYAoKTmV4dCwgd2Ugd2lsbCBgc291cmNlYCB0aGUgZnVuY3Rpb25zIEkndmUgY3JlYXRlZCB0byBkZXJpdmUgd2F0ZXIgeWVhciBpbmZvcm1hdGlvbiBhbmQgY29tcHV0ZSBzbm93IG1ldHJpY3MuIFRoZXNlIGZpbGVzIGNhbiBiZSBmb3VuZCBpbiBgYW5hbHlzaXMvZnVuY3Rpb25zYC4KCmBgYHtyfQpzb3VyY2UoImZ1bmN0aW9ucy9zbm93X21ldHJpY3MuUiIpCnNvdXJjZSgiZnVuY3Rpb25zL21ldGVvX21ldHJpY3MuUiIpCnNvdXJjZSgiZnVuY3Rpb25zL3dhdGVyX3llYXIuUiIpCmBgYAoKWW91IGNhbiBzZWUgdGhlc2UgZnVuY3Rpb25zIG5vdyBpbiB5b3VyIEVudmlyb25tZW50IHBhbmUuIEFsbCB0aGUgYHNvdXJjZWAgZnVuY3Rpb24gZG9lcyBpcyBydW4gdGhlIFIgc2NyaXB0cyB0aGF0IGNvbnRhaW4gdGhlIGZ1bmN0aW9ucyBJJ3ZlIGJ1aWx0LiAoV2UnbGwgcmV0dXJuIHRvIHRoZSBhY3R1YWwgZnVuY3Rpb25zIGxhdGVyLikKCjxpbWcgc3JjPSIuLi9pbWFnZXMvcnN0dWRpb19wYWNrYWdlX3BhbmUucG5nIiAgd2lkdGg9IjUwMCIvPgoKIyBEZWZpbmluZyBvdXIgc3Vic2V0CgojIyBVc2VmdWwgbWV0YWRhdGEKCkluIG91ciB3b3JrIHdlIHdpbGwgd2FudCB0byB1c2UgYWxsIG9yIG1vc3Qgb2YgdGhlIGxvbmctdGVybSBTTk9URUwgcmVjb3JkcywgYnV0IGhlcmUgd2UnbGwgc3RhcnQgd2l0aCBhIHN1YnNldC4gVG8gc3RhcnQgYnVpbGRpbmcgdGhpcyBzdWJzZXQsIHdlJ2xsIG5lZWQgYSBjb3VwbGUgZGF0YSBzb3VyY2VzLiBPbmUgaXMgdGhlIFNOT1RFTCBtZXRhZGF0YSB3ZSBjYW4gZ3JhYiBmcm9tIGBzbm90ZWxyYDoKCmBgYHtyfQpzbm90ZWxfaW5mbyA8LSBzbm90ZWxfaW5mbygpCnNub3RlbF9pbmZvICU+JSAKICBoZWFkKCkKYGBgCgpUaGUgb3RoZXIgaXMgdGhlIEhBUkJPUiBkYXRhc2V0IGZyb20gU2NvdHQgUGVja2hhbToKCmBgYHtyfQpoYXJib3JfdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVja2hhbXMvbmV4dGdlbl9iYXNpbl9yZXBvL3JlZnMvaGVhZHMvbWFpbi9fX0NvbGxhdGVkL2NvbGxhdGVkX2Jhc2luc19hbGwudHN2IgpiYXNpbl9pbmZvIDwtIHJlYWRfdHN2KGhhcmJvcl91cmwpCmJhc2luX2luZm8gJT4lIAogIGhlYWQoKQpgYGAKCldlIGRpc2N1c3NlZCB0aGUgU05PVEVMIGZpbGUgaW4gb3VyIHByZXZpb3VzIG5vdGVib29rLiBUaGUgSEFSQk9SIChIYXJtb25pemVkIEF0dHJpYnV0ZXMgb2YgUml2ZXIgQmFzaW5zIGluIE9uZSBSZXBvKSBkYXRhc2V0IGlzIGFuIGV4aGF1c3RpdmUgYWNjb3VudGluZyBvZiBzb21ldGltZXMgb3ZlcmxhcHBpbmcgYmFzaW4gZGF0YSBzb3VyY2VzIGZyb20gVVNHUyBOV0lTLCBDQU1FTFMsIE5XUyBSaXZlciBGb3JlY2FzdCBDZW50ZXJzLCBldGMuCgpBdCBmaXJzdCBnbGFuY2UgdGhlc2UgdHdvIGRhdGFzZXRzIGhhdmUgbm90aGluZyBpbiBjb21tb24sIGJ1dCBidXJpZWQgaW4gdGhlIGBkZXNjcmlwdGlvbmAgY29sdW1uIG9mIGBzbm90ZWxfaW5mb2AgaXMgYSBIVUMgKGh5ZHJvbG9naWMgdW5pdCBjb2RlKSBJRCB0aGF0IHdlIGNhbiBtYXRjaCB0byB0aGUgYEhVQ2AgY29sdW1uIGluIGBiYXNpbl9pbmZvYC4gV2UgaGF2ZSB0byBkbyBhIGJpdCBvZiBzdHJpbmcgbWFuaXB1bGF0aW9uIGZpcnN0LgoKYGBge3J9CnNub3RlbF9pbmZvIDwtIHNub3RlbF9pbmZvICU+JSAKICBtdXRhdGUoSFVDID0gc3RyaW5ncjo6c3RyX2V4dHJhY3Qoc3RyaW5nID0gZGVzY3JpcHRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIig/PD1cXCgpLiooPz1cXCkpIikpCmBgYAoKTm93IHdlIHRoYXQgd2UndmUgZXh0cmFjdGVkIHRoZSBIVUMgSUQgZnJvbSBpbiBiZXR3ZWVuIHRoZSBwYXJlbnRoZXNlcywgd2UgY2FuIGpvaW4gdGhlIHR3byBkYXRhc2V0cy4KCmBgYHtyfQphbGxfaW5mbyA8LSBsZWZ0X2pvaW4oc25vdGVsX2luZm8sCiAgICAgICAgICAgICAgICAgYmFzaW5faW5mbywKICAgICAgICAgICAgICAgICBieSA9ICJIVUMiKQpgYGAKClRoZSB3YXJuaW5nIGFib3ZlIGluZGljYXRlcyB0aGVyZSBhcmUgc29tZSByb3dzIGluIGBzbm90ZWxfaW5mb2AgdGhhdCBoYXZlIG11bHRpcGxlIG1hdGNoZXMgaW4gYGJhc2luX2luZm9gIGFuZCB2aWNlIHZlcnNhLiBUaGlzIG9jY3VycyB3aGVuIHRoZXJlIGFyZSBtdWx0aXBsZSBTTk9URUwgc3RhdGlvbnMgaW4gYSBnaXZlbiBIVUMgb3Igd2hlbiBhIFNOT1RFTCBzdGF0aW9uIGZpbmRzIGl0c2VsZiBpbiBtdWx0aXBsZSBuZXN0ZWQgYmFzaW5zLiAKCiMjIFVzaW5nIG1ldGFkYXRhIHRvIHNlbGVjdCBTTk9URUwgc3RhdGlvbnMKClRoZSBjb21iaW5lZCBkYXRhZnJhbWUgaW5jbHVkZXMgbXVsdGlwbGUgY29sdW1ucyB3ZSBjYW4gdXNlIHRvIHNwbGl0IHRoZSBkYXRhLiBBIGZldyBleGFtcGxlczoKCi0gQWxsIFNOT1RFTCBzdGF0aW9ucyBpbiBhIFVTR1MgR0FHRVMgSUkgUmVmZXJlbmNlIGJhc2luCiAgLSBgciBhbGxfaW5mbyAlPiUgZmlsdGVyKElzX0dBR0VTMl9SZWYgPT0gIlkiKSAlPiUgbnJvdygpYCBtYXRjaGVzCi0gU05PVEVMIHN0YXRpb25zIGFib3ZlIDE1MDAgbSBpbiBPcmVnb24gCiAgLSBgciBhbGxfaW5mbyAlPiUgZmlsdGVyKHN0YXRlID09ICJPUiIgJiBlbGV2ID4gMTUwMCkgJT4lIG5yb3coKWAgbWF0Y2hlcwotIFNOT1RFTCBzdGF0aW9ucyBpbiB0aGUgV2VzdE10bnMgZWNvcmVnaW9uIHdpdGhpbiBhIENBTUVMUyBiYXNpbiB3aXRoIGEgc25vdy1kb20gaHlkcm9ncmFwaCB0eXBlCiAgLSBgciBhbGxfaW5mbyAlPiUgZmlsdGVyKEVjb19SZWdpb24gPT0gIldlc3RNbnRzIiAmIElzX0NBTUVMUyA9PSAiWSIgJiBIZ3JhcGhfVHlwZSA9PSAic25vdy1kb20iKSAlPiUgbnJvdygpYCBtYXRjaAotIEFuZCBzbyBvbi4uLgoKRm9yIG5vdywgd2UnbGwgc3RhcnQgd2l0aCBhIHJlbGF0aXZlbHkgc21hbGwgU05PVEVMIGFuZCBVU0dTIEdhZ2VzIElJIFJlZmVyZW5jZSBzdWJzZXQgd2l0aCBhdCBsZWFzdCA0MCB5cnMgb2YgZGF0YS4KCmBgYHtyfQpzdWJzZXRfaW5mbyA8LSBhbGxfaW5mbyAlPiUgCiAgZmlsdGVyKHllYXIoc3RhcnQpIDw9IDE5ODUgJiB5ZWFyKGVuZCkgPj0gMjAyNCkgJT4lIAogIGZpbHRlcihJc19HQUdFUzJfUmVmID09ICJZIikKYGBgCgpUaGVyZSBhcmUgc29tZSBkdXBsaWNhdGVzIGluIHRoZSBzdWJzZXQsIHNvIHdlJ2xsIGZpbHRlciB0byBqdXN0IHRoZSBoaWdoZXN0IGVsZXZhdGlvbiBiYXNpbnMgKGFzc3VtaW5nIHRoZXkncmUgbW9yZSByZXByZXNlbnRhdGl2ZSBvZiB0aGUgU05PVEVMLW9ic2VydmVkIHNub3cgY29uZGl0aW9ucykuCgpgYGB7cn0Kc3Vic2V0X2luZm8gPC0gc3Vic2V0X2luZm8gJT4lIAogIGdyb3VwX2J5KHNpdGVfaWQpICU+JSAKICBzbGljZV9tYXgob3JkZXJfYnkgPSBFbGV2LCB3aXRoX3RpZXMgPSBGQUxTRSkgJT4lIAogIHVuZ3JvdXAoKQpgYGAKCk5vdyB3ZSBoYXZlIG91ciBzdWJzZXQgb2YgYHIgc3Vic2V0X2luZm8gJT4lIG5yb3coKWAgU05PVEVMIHN0YXRpb25zLgoKIyBTTk9URUwgZGF0YQoKIyMgQWNjZXNzaW5nIHN0YXRpb24gZGF0YQoKTm93IHdlJ2xsIGlkZW50aWZ5IHRoZSBgc2l0ZV9pZGAgZm9yIGVhY2ggc3RhdGlvbiBpbiBvdXIgc3Vic2V0LCBwdXQgaXQgaW4gYSB2ZWN0b3IsIGFuZCBkb3dubG9hZCB0aGUgZGF0YSB3aXRoIGBzbm90ZWxyYC4KCmBgYHtyfQpzaXRlcyA8LSBzdWJzZXRfaW5mbyAlPiUgcHVsbChzaXRlX2lkKQpgYGAKCioqTk9URTogSWYgeW91IGRvbid0IHdhbnQgdG8gd2FpdCB3aGlsZSBzbm90ZWxyIGRvd25sb2FkcyB0aGUgZGF0YXNldCwgc2tpcCBhaGVhZCB0byB0aGUgY29tbWVudGVkLW91dCBjZWxsIHRoYXQgc2F5cyBgZGYgPC0gcmVhZFJEUygiLi4vZGF0YS9zbm90ZWxfY2FtZWxzX3N1YnNldC5SRFMiKWAsIHVuY29tbWVudCBpdCwgYW5kIHJ1biBpdC4qKgoKYGBge3J9CnRpbWVfc3RhcnQgPSBTeXMudGltZSgpCmRmIDwtIHNub3RlbF9kb3dubG9hZChzaXRlcywgaW50ZXJuYWwgPSBUKQp0aW1lX2VuZCA9IFN5cy50aW1lKCkKdGltZV9lbmQgLSB0aW1lX3N0YXJ0CmBgYAoKRmlyc3QsIHdlJ2xsIHNlbGVjdCBqdXN0IHRoZSBjb2x1bW5zIHdlIG5lZWQuCgpgYGB7cn0KIyBEb3duc2VsZWN0IHRvIGp1c3QgdGhlIGNvbHVtbnMgd2Ugd2FudAojIFJlbmFtZSB2YXIgY29sdW1ucyB0byBpbmNsdWRlIHVuaXRzCmRmIDwtIGRmICU+JSAKICBzZWxlY3Qoc2l0ZV9pZCwgZGF0ZSwKICAgICAgICAgc3dlX21tID0gc25vd193YXRlcl9lcXVpdmFsZW50LAogICAgICAgICBzbm93X2RlcHRoX21tID0gc25vd19kZXB0aCwKICAgICAgICAgcHB0X21tID0gcHJlY2lwaXRhdGlvbiwKICAgICAgICAgdGFpcl9hdl9kZWdDID0gdGVtcGVyYXR1cmVfbWVhbikKYGBgCgpUaGVuIHdlJ2xsIGFkZCBkYXRlIGluZm9ybWF0aW9uOgoKYGBge3J9CiMgQWRkIGFkZGl0aW9uYWwgZGF0ZSBpbmZvcm1hdGlvbgpkZiA8LSBkZiAlPiUgCiAgbXV0YXRlKGRhdGUgPSB5bWQoZGF0ZSksCiAgICAgICAgIHd5ZWFyID0gd2F0ZXJ5ZWFyKGRhdGUpLAogICAgICAgICBkb3d5ID0gZGF5X29mX3dhdGVyeWVhcihkYXRlKSkKYGBgCgpUaGVuIHdlJ2xsIHNhdmUgaXQgYXMgYW4gUkRTIGZpbGUgKEkndmUgY29tbWVudGVkIHRoaXMgcGFydCBvdXQgYmVjYXVzZSBpdCBkb2Vzbid0IG5lZWQgdG8gcmUtcnVuKS4KCipOb3RlOiBJIHNhdmVkIHRoZSBmaWxlIGZvciB0d28gcmVhc29uczogMSkgaW4gY2FzZSB3ZSBoYXZlIGJhbmR3aWR0aCBpc3N1ZXMgYW5kIDIpIHRvIGFuYWx5emUgYWdhaW4gaW4gdGhlIG5leHQgbm90ZWJvb2suKgoKYGBge3J9CiMgc2F2ZVJEUyhvYmplY3QgPSBkZiwKIyAgICAgICAgIGZpbGUgPSAiLi4vZGF0YS9zbm90ZWxfY2FtZWxzX3N1YnNldC5SRFMiKQpgYGAKCldlIGNhbiB1bmNvbW1lbnQgb3V0IHRoZSBmb2xsb3dpbmcgaWYgd2UgbmVlZCB0byBpbXBvcnQgdGhlIHNhdmVkIGRhdGEuCgpgYGB7cn0KIyBkZiA8LSByZWFkUkRTKCIuLi9kYXRhL3Nub3RlbF9jYW1lbHNfc3Vic2V0LlJEUyIpCmBgYAoKIyMgUHJlLXByb2Nlc3NpbmcgdGhlIGRhdGEKCldlIHdhbnQgdG8gb25seSBpbmNsdWRlIHllYXJzIGluIG91ciBhbmFseXNpcyB3aXRoIGEgY2VydGFpbiBwZXJjZW50YWdlIG9mIHZhbGlkIFNXRSBvYnNlcnZhdGlvbnMuIFdlJ3JlIGdvaW5nIHRvIG1ha2UgdGhhdCB0aHJlc2hvbGQgMTAwJSBoZXJlIChTTk9URUwgU1dFIGhhcyBhIHJlbGF0aXZlbHkgcm9idXN0IFFDIHByb3RvY29sKSwgYnV0IHlvdSBjYW4gY2hvb3NlIG90aGVyIHZhbHVlcy4KCmBgYHtyfQojIENhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiB2YWxpZCBTV0Ugb2JzZXJ2YXRpb25zIHBlciB3YXRlciB5ZWFyCnNpdGVfc3VtbWFyeV9ieV93eWVhciA8LSBkZiAlPiUgCiAgZ3JvdXBfYnkoc2l0ZV9pZCwgd3llYXIpICU+JSAKICBzdW1tYXJpemUobl9leHBlY3RlZCA9IGlmZWxzZShhbnkod3llYXIgJSUgNCA9PSAwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAzNjYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMzY1KSwKICAgICAgICAgICAgbl9vYnMgPSBzdW0oIWlzLm5hKHN3ZV9tbSkpLAogICAgICAgICAgICBwY3RfdmFsaWQgPSAobl9vYnMgLyBuX2V4cGVjdGVkKSAqIDEwMCkgJT4lIAogIHVuZ3JvdXAoKQoKIyBQcm92aWRlIGEgdGhyZXNob2xkIG9mIHZhbGlkIG9icyB0aGF0IHdlJ2xsIGNvbnNpZGVyIHRvIGJlIGEgY29tcGxldGUgd2F0ZXIgeWVhcgpwY3RfdmFsaWRfdGhyZXNoID0gMTAwCgojIElkZW50aWZ5IHNpdGVzIGFuZCB3YXRlciB5ZWFycyB0aGF0IG1lZXQgb3VyIHRocmVzaG9sZAp2YWxpZF9zaXRlc193eWVhcnMgPC0gc2l0ZV9zdW1tYXJ5X2J5X3d5ZWFyICU+JSAKICBmaWx0ZXIocGN0X3ZhbGlkID49IHBjdF92YWxpZF90aHJlc2gpICU+JSAKICBzZWxlY3Qoc2l0ZV9pZCwgd3llYXIpCgojIEZpbHRlciB1c2luZyBpbm5lciBqb2luIHRvIG9ubHkgc2l0ZXMgYW5kIHdhdGVyIHllYXJzIGluIG91ciB2YWxpZCBkYXRhIGZyYW1lCmRmX2ZpbHRlciA8LSAKICBpbm5lcl9qb2luKGRmLCB2YWxpZF9zaXRlc193eWVhcnMsCiAgICAgICAgICAgICBieSA9IGMoInNpdGVfaWQiLCAid3llYXIiKSkKYGBgCgojIENhbGN1bGF0ZSBtZXRyaWNzCgpOb3cgd2UnbGwgdXNlIG91ciBTTk9URUwgZGF0YSBzdWJzZXQgdG8gY29tcHV0ZSB2YXJpb3VzIG1ldHJpY3M6CgotIE1heGltdW0gc25vdyB3YXRlciBlcXVpdmFsZW50IChTV0UpCi0gTWF4aW11bSBTV0UgZGF5IG9mIHdhdGVyIHllYXIgKERPV1kpCi0gU25vdyBjb3ZlciBkdXJhdGlvbgotIFNub3ctb24gZGF5Ci0gU25vdy1vZmYgZGF5Ci0gTWVsdCBzZWFzb24gbGVuZ3RoCi0gU25vd21lbHQgcmF0ZQotIFNub3cgc2Vhc29uYWxpdHkgbWV0cmljIChTU00pCi0gQXByaWwgMSBTV0UKLSBTbm93bWVsdCBiZWZvcmUgbWF4IFNXRQotIFNub3dtZWx0IGJlZm9yZSBtYXggU1dFIHBlcmNlbnQgb2YgdG90YWwgc25vd21lbHQKLSBTbm93bWVsdCBiZWZvcmUgbWF4IFNXRSB0byBtYXggU1dFIHJhdGlvCi0gU25vd21lbHQgY2VudGVyIG9mIG1hc3MgRE9XWQotIFBlYWsgU1dFIHRvIGFubnVhbCBwcmVjaXBpdGF0aW9uIHJhdGlvCi0gQW5kIHNldmVyYWwgbWV0ZW9yb2xvZ2ljYWwgZGF0YSBtZXRyaWNzCgpgYGB7cn0KbWV0cmljcyA8LSBkZl9maWx0ZXIgJT4lIAogIGdyb3VwX2J5KHNpdGVfaWQsIHd5ZWFyKSAlPiUgCiAgc3VtbWFyaXplKG1heF9zd2VfbW0gPSBtYXhTV0Uoc3dlX21tKSwgCiAgICAgICAgICAgIG1heF9zd2VfZG93eSA9IG1heFNXRV9ET1dZKHN3ZV9tbSwgZG93eSksIAogICAgICAgICAgICBzY2RfZGF5cyA9IHNjZChzd2VfbW0pLAogICAgICAgICAgICBzbm93X29uX2RheSA9IGZpcnN0U25vdyhzd2VfbW0sIGRvd3kpLAogICAgICAgICAgICBzbm93X29mZl9kYXkgPSBsYXN0U25vdyhzd2VfbW0sIGRvd3ksIG1heF9zd2VfZG93eSksCiAgICAgICAgICAgIG1lbHRfc2Vhc29uX2RheXMgPSBtZWx0U2Vhc29uKG1heF9zd2VfZG93eSwgc25vd19vZmZfZGF5KSwKICAgICAgICAgICAgbWVsdF9yYXRlX21tX2QgPSBtZWx0UmF0ZShtZWx0X3NlYXNvbl9kYXlzLCBtYXhfc3dlX21tKSwKICAgICAgICAgICAgc3NtID0gc25vd1NlYXNvbmFsaXR5KHN3ZV9tbSksIAogICAgICAgICAgICBzd2VfYXByMV9tbSA9IGFwcmlsMVNXRShzd2VfbW0sIGRvd3ksIHd5ZWFyKSwgCiAgICAgICAgICAgIHByZV9tYXhfc3dlX21lbHRfbW0gPSBwcmVNYXhNZWx0KHN3ZV9tbSwgZG93eSwgbWF4X3N3ZV9kb3d5KSwKICAgICAgICAgICAgcHJlX21heF9zd2VfbWVsdF90b3RhbF9tZWx0X3BjdCA9IHByZU1heE1lbHRQY3RUb3RhbE1lbHQocHJlX21heF9zd2VfbWVsdF9tbSwgc3dlX21tKSwKICAgICAgICAgICAgcHJlX21heF9zd2VfbWVsdF9tYXhfc3dlX3JhdGlvID0gcHJlTWF4TWVsdE1heFNXRVJhdGlvKHByZV9tYXhfc3dlX21lbHRfbW0sIG1heF9zd2VfbW0pLAogICAgICAgICAgICBtZWx0X2NvbV9kYXkgPSBtZWx0Q29NKHN3ZV9tbSwgZG93eSksCiAgICAgICAgICAgIHN3ZV90b19wcHRfcmF0aW8gPSBzd2VUb1BwdFJhdGlvKG1heF9zd2VfbW0sIHBwdF9tbSksCiAgICAgICAgICAgIGZhbGxfcHB0X21tID0gc2Vhc29uYWxQcmVjaXAocHB0X21tLCBkYXRlLCBTRUFTT04gPSAiZmFsbCIpLAogICAgICAgICAgICB3aW50ZXJfcHB0X21tID0gc2Vhc29uYWxQcmVjaXAocHB0X21tLCBkYXRlLCBTRUFTT04gPSAid2ludGVyIiksCiAgICAgICAgICAgIHNwcmluZ19wcHRfbW0gPSBzZWFzb25hbFByZWNpcChwcHRfbW0sIGRhdGUsIFNFQVNPTiA9ICJzcHJpbmciKSwKICAgICAgICAgICAgc3VtbWVyX3BwdF9tbSA9IHNlYXNvbmFsUHJlY2lwKHBwdF9tbSwgZGF0ZSwgU0VBU09OID0gInN1bW1lciIpLAogICAgICAgICAgICBhbm51YWxfcHB0X21tID0gdG90YWxQcmVjaXAocHB0X21tKSwKICAgICAgICAgICAgZmFsbF90YWlyX2RlZ0MgPSBzZWFzb25hbFRlbXAodGFpcl9hdl9kZWdDLCBkYXRlLCBTRUFTT04gPSAiZmFsbCIpLAogICAgICAgICAgICB3aW50ZXJfdGFpcl9kZWdDID0gc2Vhc29uYWxUZW1wKHRhaXJfYXZfZGVnQywgZGF0ZSwgU0VBU09OID0gIndpbnRlciIpLAogICAgICAgICAgICBzcHJpbmdfdGFpcl9kZWdDID0gc2Vhc29uYWxUZW1wKHRhaXJfYXZfZGVnQywgZGF0ZSwgU0VBU09OID0gInNwcmluZyIpLAogICAgICAgICAgICBzdW1tZXJfdGFpcl9kZWdDID0gc2Vhc29uYWxUZW1wKHRhaXJfYXZfZGVnQywgZGF0ZSwgU0VBU09OID0gInN1bW1lciIpLAogICAgICAgICAgICBhbm51YWxfdGFpcl9kZWdDID0gbWVhblRlbXAodGFpcl9hdl9kZWdDKSkKYGBgCgpgYGB7cn0KIyBFeHBvcnQgZGF0YSAKc2F2ZVJEUyhvYmplY3QgPSBtZXRyaWNzLAogICAgICAgIGZpbGUgPSAiLi4vZGF0YS9zbm90ZWxfY2FtZWxzX3N1YnNldF9tZXRyaWNzLlJEUyIpCmBgYAoKCldlIGNhbiB0YWtlIGEgcXVpY2sgbG9vayBhdCB0aGVzZSB0YWJ1bGFyIGRhdGEuCgpgYGB7cn0KbWV0cmljcyAlPiUgCiAgaGVhZCgpCmBgYAoKV2UgY2FuIGFsc28gcGxvdCBzb21lIG9mIHRoZSBvdXRjb21lcy4KCiMjIE1heGltdW0gU1dFCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD03LjV9CmdncGxvdChtZXRyaWNzLCBhZXMod3llYXIsIG1heF9zd2VfbW0pKSArIAogIGdlb21fbGluZSgpICsgCiAgZmFjZXRfd3JhcCh+YXMuZmFjdG9yKHNpdGVfaWQpLCBuY29sID0gNCkgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJNYXggU1dFIChtbSkiKQoKYGBgCgojIFNub3cgY292ZXIgZHVyYXRpb24gKFNDRCkKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTcuNX0KZ2dwbG90KG1ldHJpY3MsIGFlcyh3eWVhciwgc2NkX2RheXMpKSArIAogIGdlb21fbGluZSgpICsgCiAgZmFjZXRfd3JhcCh+YXMuZmFjdG9yKHNpdGVfaWQpLCBuY29sID0gNCkgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJTQ0QgKGQpIikKCmBgYAoKIyMgU25vdyBzZWFzb25hbGl0eSBtZXRyaWMgKFNTTSkKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTcuNX0KZ2dwbG90KG1ldHJpY3MsIGFlcyh3eWVhciwgc3NtKSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihzaXRlX2lkKSwgbmNvbCA9IDQpICsKICBsYWJzKHggPSAiV2F0ZXIgWWVhciIsIHkgPSAiU1NNIikKCmBgYAoKIyMgUHJlLW1heCBTV0Ugc25vd21lbHQgYXMgcGVyY2VudCBvZiB0b3RhbCBzbm93bWVsdAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9Ny41fQpnZ3Bsb3QobWV0cmljcywgYWVzKHd5ZWFyLCBwcmVfbWF4X3N3ZV9tZWx0X3RvdGFsX21lbHRfcGN0KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihzaXRlX2lkKSwgbmNvbCA9IDQpICsKICBsYWJzKHggPSAiV2F0ZXIgWWVhciIsIHkgPSAiUHJlLU1heCBTV0UgTWVsdCAoJSkiKQoKYGBgCgpIb3dldmVyLCBpdCBpcyBoYXJkIHRvIHRlbGwgd2hhdCwgaWYgYW55dGhpbmcsIGlzIGhhcHBlbmluZyBhdCBvdXIgc2l0ZXMgb3ZlciB0aW1lLiBTbyB3aGF0IHdlJ2xsIGRvIG5leHQgaXMgY29tcHV0ZSBzb21lIHRyZW5kcy4KCiMgQ29tcHV0ZSB0cmVuZHMKCkZpcnN0IHdlJ2xsIG1ha2UgYSBmdW5jdGlvbiB0byBtYWtlIGEgImxvbmciIHZlcnNpb24gb2Ygb3VyIGBtZXRyaWNzYCBkYXRhZnJhbWUgYW5kIHRoZW4gY29tcHV0ZSB2YXJpb3VzIHRyZW5kIHN0YXRzLCBzdWNoIGFzIHRoZSBNYW5uLUtlbmRhbGwgcC12YWx1ZSBhbmQgU2VuJ3Mgc2xvcGUuCgpgYGB7cn0KYW5hbHl6ZV9zbm93X3RyZW5kcyA8LSBmdW5jdGlvbihERikgewogICMgUGl2b3QgdG8gbWFrZSBsb25nIGRhdGFmcmFtZQogIGRmX2xvbmcgPC0gREYgJT4lCiAgICBwaXZvdF9sb25nZXIoCiAgICAgIGNvbHMgPSAtYyhzaXRlX2lkLCB3eWVhciksICMgY291bGQgYWRkIGNvbHVtbiBuYW1lcyBhcyBhcmd1bWVudCB0byBtYWtlIGZ1bmN0aW9uIGdlbmVyYWxpemFibGUKICAgICAgbmFtZXNfdG8gPSAibWV0cmljIiwKICAgICAgdmFsdWVzX3RvID0gInZhbHVlIgogICAgKSAlPiUKICAgIGZpbHRlcighaXMubmEodmFsdWUpKSAlPiUgICAgIyByZW1vdmUgTkFzCiAgICBmaWx0ZXIoIWlzLmluZmluaXRlKHZhbHVlKSkgICMgcmVtb3ZlIEluZnMKICAKICAjIFRha2UgZGZfbG9uZyBhbmQgY29tcHV0ZSB0cmVuZCB2YWx1ZXMgcGVyIHNpdGVfaWQgYW5kIG1ldHJpYwogIGRmX2xvbmcgJT4lCiAgICBncm91cF9ieShzaXRlX2lkLCBtZXRyaWMpICU+JQogICAgc3VtbWFyaXNlKAogICAgICBuX3llYXJzID0gbigpLAogICAgICBtYW5uX2tlbmRhbGwgPSBsaXN0KE1hbm5LZW5kYWxsKHZhbHVlKSksCiAgICAgIHNlbnNfc2xvcGUgPSBsaXN0KHNlbnMuc2xvcGUodmFsdWUpKSwKICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgKSAlPiUKICAgIG11dGF0ZSgKICAgICAgbWtfdGF1ID0gbWFwX2RibChtYW5uX2tlbmRhbGwsIH4gLngkdGF1KSwKICAgICAgbWtfcCA9IG1hcF9kYmwobWFubl9rZW5kYWxsLCB+IC54JHNsKSwKICAgICAgc2VuX3Nsb3BlID0gbWFwX2RibChzZW5zX3Nsb3BlLCB+IC54JGVzdGltYXRlcyksCiAgICAgIHNlbl9wID0gbWFwX2RibChzZW5zX3Nsb3BlLCB+IC54JHAudmFsdWUpCiAgICApICU+JQogICAgc2VsZWN0KHNpdGVfaWQsIG1ldHJpYywgbl95ZWFycywgbWtfdGF1LCBta19wLCBzZW5fc2xvcGUsIHNlbl9wKQp9CmBgYAoKTm93IHdlJ2xsIGFwcGx5IHRoaXMgZnVuY3Rpb24gdG8gYG1ldHJpY3NgLgoKYGBge3J9CnRyZW5kcyA8LSBhbmFseXplX3Nub3dfdHJlbmRzKG1ldHJpY3MpCnRyZW5kcyAlPiUgCiAgaGVhZCgpCmBgYAoKYGBge3J9CiMgRXhwb3J0IHRyZW5kcyBkYXRhCnNhdmVSRFMob2JqZWN0ID0gdHJlbmRzLAogICAgICAgIGZpbGUgPSAiLi4vZGF0YS9zbm90ZWxfY2FtZWxzX3N1YnNldF90cmVuZHMuUkRTIikKYGBgCgoKV2UgY2FuIHZpZXcgZGlzdHJpYnV0aW9ucyBvZiB0aGUgTWFubi1LZW5kYWxsIHAtdmFsdWVzIGFuZCBTZW4ncyBzbG9wZXMuCgpgYGB7ciBmaWcuaGVpZ2h0PTkuNSwgZmlnLndpZHRoPTcuNX0KZ2dwbG90KHRyZW5kcywgYWVzKG1rX3ApKSArCiAgZ2VvbV9kZW5zaXR5KGZpbGwgPSAibGlnaHRibHVlIikgKyAKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLjA1LCBsdHkgPSAiZGFzaGVkIikgKyAKICBmYWNldF93cmFwKH5tZXRyaWMsIG5jb2wgPSAzLCBzY2FsZXMgPSAiZnJlZSIpICsKICBsYWJzKHggPSAiTWFubi1LZW5kYWxsIHAtdmFsdWVzIiwgeSA9ICJEZW5zaXR5IikKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTkuNSwgZmlnLndpZHRoPTcuNX0KZ2dwbG90KHRyZW5kcywgYWVzKHNlbl9zbG9wZSkpICsKICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJsaWdodGJsdWUiKSArIAogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGx0eSA9ICJkYXNoZWQiKSArIAogIGZhY2V0X3dyYXAofm1ldHJpYywgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnMoeCA9ICJTZW4ncyBTbG9wZXMiLCB5ID0gIkRlbnNpdHkiKQpgYGAKCldlIGNhbiBub3cgcmUtZXhhbWluZSBzb21lIG9mIHRoZSBwcmV2aW91cyBwbG90cywgbG9va2luZyBhdCBvbmx5IHNpdGVzIHdpdGggc2lnbmlmaWNhbnQgY2hhbmdlcy4KCiMjIE1heGltdW0gU1dFIHdpdGggc3RhdGlzdGljYWxseSBzaWdpbmlmaWNhbnQgdHJlbmRzCgpgYGB7cn0KcF90aHJlc2ggPSAwLjA1Cm1ldHJpY3MgJT4lIAogIGZpbHRlcihzaXRlX2lkICVpbiUgZmlsdGVyKHRyZW5kcywgbWV0cmljID09ICJtYXhfc3dlX21tIiAmIG1rX3AgPCAwLjA1KSRzaXRlX2lkKSAlPiUgCiAgZ2dwbG90KGFlcyh3eWVhciwgbWF4X3N3ZV9tbSkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEYsIGNvbG9yID0gInJlZCIpICsKICBmYWNldF93cmFwKH5hcy5mYWN0b3Ioc2l0ZV9pZCksIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJNYXggU1dFIChtbSkiKQpgYGAKCiMjIFNDRCB3aXRoIHN0YXRpc3RpY2FsbHkgc2lnaW5pZmljYW50IHRyZW5kcwoKYGBge3J9Cm1ldHJpY3MgJT4lIAogIGZpbHRlcihzaXRlX2lkICVpbiUgZmlsdGVyKHRyZW5kcywgbWV0cmljID09ICJzY2RfZGF5cyIgJiBta19wIDwgMC4wNSkkc2l0ZV9pZCkgJT4lIAogIGdncGxvdChhZXMod3llYXIsIHNjZF9kYXlzKSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRiwgY29sb3IgPSAicmVkIikgKwogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihzaXRlX2lkKSwgc2NhbGVzID0gImZyZWUiKSArCiAgbGFicyh4ID0gIldhdGVyIFllYXIiLCB5ID0gIlNDRCAoZCkiKQpgYGAKCiMjIFNub3cgc2Vhc29uYWxpdHkgbWV0cmljIChTU00pIHdpdGggc3RhdGlzdGljYWxseSBzaWdpbmlmaWNhbnQgdHJlbmRzCgpgYGB7cn0KbWV0cmljcyAlPiUgCiAgZmlsdGVyKHNpdGVfaWQgJWluJSBmaWx0ZXIodHJlbmRzLCBtZXRyaWMgPT0gInNzbSIgJiBta19wIDwgMC4wNSkkc2l0ZV9pZCkgJT4lIAogIGdncGxvdChhZXMod3llYXIsIHNzbSkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEYsIGNvbG9yID0gInJlZCIpICsKICBmYWNldF93cmFwKH5hcy5mYWN0b3Ioc2l0ZV9pZCksIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJTU00iKQpgYGAKCiMjIFByZS1tYXggU1dFIHNub3dtZWx0IGFzIHBlcmNlbnQgb2YgdG90YWwgc25vd21lbHQgd2l0aCBzdGF0aXN0aWNhbGx5IHNpZ2luaWZpY2FudCB0cmVuZHMKCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03LjV9Cm1ldHJpY3MgJT4lIAogIGZpbHRlcihzaXRlX2lkICVpbiUgZmlsdGVyKHRyZW5kcywgbWV0cmljID09ICJwcmVfbWF4X3N3ZV9tZWx0X3RvdGFsX21lbHRfcGN0IiAmIG1rX3AgPCAwLjA1KSRzaXRlX2lkKSAlPiUgCiAgZ2dwbG90KGFlcyh3eWVhciwgcHJlX21heF9zd2VfbWVsdF90b3RhbF9tZWx0X3BjdCkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEYsIGNvbG9yID0gInJlZCIpICsKICBmYWNldF93cmFwKH5hcy5mYWN0b3Ioc2l0ZV9pZCksIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9MykgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJQcmUtTWF4IFNXRSBNZWx0ICglKSIpCmBgYAoKV2UgY2FuIHN1bW1hcml6ZSB0aGUgdHJlbmRzIGV2ZW4gZnVydGhlciB0byBzZWUgd2hpY2ggb25lcyBoYXZlIHRoZSBtb3N0IHByZXZhbGVudCBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IHJlc3VsdHMuCgpgYGB7cn0KcF90aHJlc2ggPSAwLjA1CnRyZW5kX3N1bW1hcnkgPC0gdHJlbmRzICU+JSAKICBncm91cF9ieShtZXRyaWMpICU+JSAKICBzdW1tYXJpemUobWtfcGN0X3NpZyA9IChzdW0obWtfcCA8IHBfdGhyZXNoKSAvIG4oKSkgKiAxMDAsCiAgICAgICAgICAgIHNlbl9zbG9wZV9hdiA9IG1lYW4oc2VuX3Nsb3BlKSwKICAgICAgICAgICAgc2VuX3Nsb3BlX2F2X3NpZyA9IG1lYW4oc2VuX3Nsb3BlW21rX3AgPCBwX3RocmVzaF0pKQp0cmVuZF9zdW1tYXJ5ICU+JSAKICBhcnJhbmdlKC1ta19wY3Rfc2lnKQpgYGAKCk5vdyB0aGF0IHdlJ3ZlIGxvb2tlZCBhdCB0aGVzZSBkYXRhLCB3ZSdsbCBleHBsb3JlIHRoZSB1c2Ugb2Ygc3BhdGlhbCBTV0UgaW5mb3JtYXRpb24gaW4gb3VyIHdvcmsu